package ui;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.IProxy;
import proxy.impl.ProxyImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 测试类
 * 今天开始学习动态代理，代理模式是java常用设计模式的一种
 * 其中代理分为静态代理和动态代理，具体的定义这里就不展开了
 * 静态代理也没什么可说的，今天我们主要了解一下动态代理。
 * 代理是spring AOP的核心，学不会代理，AOP肯定学不会
 * AOP实现的关键就在于AOP框架自动创建的AOP代理，
 * AOP代理则可分为静态代理和动态代理两大类，
 * 其中静态代理是指使用AOP框架提供的命令进行编译，
 * 从而在编译阶段就可生成 AOP 代理类，因此也称为编译时增强；
 * 而动态代理则在运行时借助于JDK动态代理、CGLIB等在内存中“临时”生成AOP动态代理类，因此也被称为运行时增强
 *
 * 说了这么多其实很晦涩，其实简单的来说 动态代理和平常点外卖差不多
 * 首先我们在平台点餐，平台会给我们找一个外卖小哥，最后外卖小哥把饭送到你的手上，
 * 最后送完餐，外卖小哥任务完成，此后你们也没有任何关系了
 *
 * 在这个事件里面，你不需要关心怎么分配的外卖小哥，至于外卖小哥怎么取餐 怎么送餐你也不用关心。
 * 映射到动态代理中
 *          点餐<-->调用某个类的某个方法
 *          分配的外卖小哥<-->临时创建的代理类
 *          外卖小哥取餐、送餐等一系列动作<-->代理类的增强方法
 *          送餐完毕，小哥任务完成<-->代理类的销毁
 *
 * 动态代理：
 *  特点：字节码随用随创建，随用随加载
 *  作用：不修改源码的基础上对方法进行增强
 *  分类：
 *      基于接口的动态代理
 *          涉及的类： Proxy
 *          提供者：JDK官方
 *          创建方法：Proxy中的newProxyInstance方法
 *          创建要求：被代理类最少实现一个接口
 *      基于子类的动态代理
 *          涉及的类：Enhancer
 *          提供者：第三方的cglib库
 *          创建方法：Enhancer中的create方法
 *          创建要求：被代理类不能是最终类，即不能被final修饰
 *
 */
public class TestProxy {
    public static void main(String[] args) {
        /**
         *  我们知道 动态代理和静态代理不同的地方在于：
         *  静态代理类是实实在在的类，是由我们自己去开发的，而动态代理类在我们的开发环境是不存在的
         *  动态代理类是随着我们项目运行，然后自动在内存中生成class文件
         *  最后由类加载器加载到jvm中,以便我们使用。
         *  为什么我们看不到编译好的动态代理类文件？
         *  因为虚拟机在将class文件加载到虚拟机中后就将class文件删除了
         *  在使用JDK动态代理时我们可以通过以下方法通知虚拟保存产生的代理文件
         *  在调用测试代理的方法前加(此方法通知虚拟保存产生的代理文件,文件路径为文件根目录/com/sun/proxy/)
         */
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        testJDKProxy();
        /**
         * CGLIB使用该方法保存产生的代理文件
         */
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");
//        testCGLIBProxy();

    }

    /**
     * 使用jdk动态代理生成代理类
     */
    public static void testJDKProxy(){
        /**
         * 定义要代理的对象
         * JDK动态代理的前提是需要被代理的类是接口的实现类
         * 如果被代理类 没有实现接口 则无法使用JDK代理
         */
        IProxy jdkProxy = new ProxyImpl();
        /**
         * 使用Proxy类提供的newProxyInstance方法来生成代理类
         * newProxyInstance参数详解
         *      Params:
         *          ClassLoader loader - 定义代理类的类加载器(同被代理类的类加载器是一致的)
         *          Class<?>[] interfaces - 代理类要实现的接口列表(同被代理类的实现的接口列表是一致的)
         *          InvocationHandler h - InvocationHandler对象 是一个接口 通常情况下都是匿名内部类，但是不必须
         *          我们需要实现其invoke方法 invoke为方法调用的处理程序(被代理类的方法的增强方法)
         *
         */
        IProxy proxy = (IProxy) Proxy.newProxyInstance(jdkProxy.getClass().getClassLoader(), jdkProxy.getClass().getInterfaces(), new InvocationHandler() {

            /**
             * @param proxy 代理类
             * @param method 被调用的方法
             * @param args 被调用的方法的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Object ret = null;
                if(method.getName().equals("order")){
                    System.out.print("接单->取餐->送餐->");
                    ret = method.invoke(jdkProxy, args);
                    System.out.println("->结束");

                }else{
                    ret = method.invoke(args);
                }
                return ret;
            }
        });

        proxy.order("test");
    }


    public static void testCGLIBProxy(){
        ProxyImpl proxyImpl = new ProxyImpl();
        /**
         * CGLIB使用Enhancer的create方法来创建动态代理对象
         * 参数解析：
         *          Class type - 被代理类的字节码文件
         *          Callback callback - 用于方法增强 一般用Callback的子接口MethodInterceptor并实现其intercept方法
         */
        ProxyImpl CGLIBProxy = (ProxyImpl)Enhancer.create(proxyImpl.getClass(), new MethodInterceptor() {
            /**
             *
             * @param proxy 代理类
             * @param method 被调用的方法
             * @param args 被调用的方法的参数
             * @param methodProxy
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                Object ret = null;
                if(method.getName().equals("order")){
                    System.out.print("接单->取餐->送餐->");
                    ret = method.invoke(proxyImpl, args);
                    System.out.println("->结束");

                }else{
                    ret = method.invoke(args);
                }
                return ret;
            }
        });
        CGLIBProxy.order("test");
    }
}
